﻿using System;
using System.Diagnostics;
using Simp.Model.CoreExpressions;

namespace Simp.Model
{
	/// <summary>Custom expression is a named expression taking parameters and applying them to a expression <c>body</c>.</summary>
	/// <remarks>This expression is expected to be composed with its body and its parameter bindings.</remarks>
	[DebuggerDisplay("{ExpressionHelper.Serialize(this)}")]
	public class CustomExpression : IExpression, INamedExpression, IParameterPolicyExpression, IExpressionSerializer
	{
		private readonly string _name;
		private readonly bool _isLambda;
		private readonly IParameterBinding[] _parameterBindings;

		/// <summary>
		/// Constructor taking the name of the expression, and a flag telling if the expression is a lambda expression.
		/// </summary>
		/// <remarks>Lambda expression have different policies ; for instance, they can use <c>ambiant</c> variables.</remarks>
		/// <param name="name"/>
		/// <param name="isLambda"/>
		/// <param name="parameterCount"/>
		public CustomExpression(string name, bool isLambda, int parameterCount)
		{
			if (string.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			if (parameterCount < 1)
			{
				throw new ArgumentException("Can't be less than one (expression body)", "parameterCount");
			}
			_name = name;
			_isLambda = isLambda;
			_parameterBindings = ExpressionHelper.CreateParameterBinding(parameterCount);
		}

		/// <summary>Returns <c>true</c> iif the custom expression represents a lambda expression.</summary>
		public bool IsLambda
		{
			get { return _isLambda; }
		}

		/// <summary>Returns the parameter bindings corresponding to the parameters of the custom expression.</summary>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public IParameterBinding[] GetParameterBinding(params IExpression[] parameters)
		{
			if (parameters == null)
			{
				throw new ArgumentNullException("parameterList");
			}
			if (parameters.Length != _parameterBindings.Length)
			{
				throw new ArgumentException("Wrong # of parameters", "parameters");
			}

			IParameterBinding[] bindings = new IParameterBinding[parameters.Length - 1];

			for (int i = 0; i != bindings.Length; ++i)
			{
				IExpression currentParameter = parameters[i + 1];
				ParameterBindingExpression parameterBindingExpression;
				CompositeExpression compositeExpression =
					ExpressionHelper.TryGetComposite(currentParameter, out parameterBindingExpression);

				if (compositeExpression != null)
				{
					bindings[i] = parameterBindingExpression.GetParameterBinding(compositeExpression.ParameterList);
				}
				else
				{
					StringExpression stringExpression = currentParameter as StringExpression;

					if (stringExpression != null)
					{
						bindings[i] = ExpressionHelper.ParameterBindingFactory.Build(stringExpression.Value, null);
					}
					else
					{
						throw new ApplicationException("Wrong binding expression type");
					}
				}
			}

			return bindings;
		}

		#region INamedExpression Members
		string INamedExpression.Name
		{
			get { return _name; }
		}
		#endregion

		#region IParameterPolicyExpression Members
		IParameterBinding[] IParameterPolicyExpression.ParameterBindings
		{
			get { return _parameterBindings; }
		}
		#endregion

		#region IExpressionSerializer Members
		void IExpressionSerializer.Serialize(AppendOnlyStringBuilder builder, params IExpression[] parameters)
		{
			if (builder == null)
			{
				throw new ArgumentNullException("builder");
			}
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}
			builder.Append(_name);

			if (parameters.Length > 1)
			{
				builder.Append("(");
				for (int i = 1; i != parameters.Length; ++i)
				{
					ParameterBindingExpression parameterBindingExpression;
					CompositeExpression compositeExpression =
						ExpressionHelper.TryGetComposite(parameters[i], out parameterBindingExpression);

					if (compositeExpression != null)
					{
						ExpressionHelper.Serialize(builder, parameterBindingExpression, compositeExpression.ParameterList);
					}
					else
					{
						StringExpression stringExpression = parameters[i] as StringExpression;

						if (stringExpression != null)
						{
							builder.Append(stringExpression.Value);
						}
						else
						{
							builder.Append("\"Wrong Binding\"");
						}
					}
					if (i != _parameterBindings.Length - 1)
					{
						builder.Append(", ");
					}
				}
				builder.Append(")");
			}
			if (parameters.Length > 0)
			{
				builder.Append(" = ");
				ExpressionHelper.Serialize(builder, parameters[0]);
			}
		}
		#endregion
	}
}